package com.example.friend.manger;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;

import com.example.core.constants.CacheConstants;
import com.example.core.constants.Constants;
import com.example.core.domain.TableDataInfo;
import com.example.core.enums.ExamListType;
import com.example.friend.domain.Exam;
import com.example.friend.domain.User;
import com.example.friend.domain.UserExam;
import com.example.friend.domain.vo.ExamRankVO;
import com.example.friend.domain.vo.ExamVO;
import com.example.friend.mapper.ExamMapper;
import com.example.friend.mapper.UserExamMapper;
import com.example.redis.service.RedisService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

@Component
public class ExamCacheManager {

    @Autowired
    private ExamMapper examMapper;

    @Autowired
    private UserExamMapper userExamMapper;

    @Autowired
    private UserCacheManager userCacheManager;

    @Autowired
    private RedisService redisService;

    public TableDataInfo selectExamList(int start, int end, Long userId, String startTime, String endTime) {
        String key = getExamListKey();
        if (startTime != null || endTime != null) {
            return getExamListByTime(ExamListType.EXAM_NOT_STARTED_LIST, key, startTime, endTime, start, end);
        }
        return getExamList(ExamListType.EXAM_NOT_STARTED_LIST, userId, key, start, end);
    }

    private TableDataInfo getExamListByTime(ExamListType examListType, String key,
                                            String startTime, String endTime,int start,int end) {
        String format = "yyyy-MM-dd HH:mm:ss";
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(format);
        List<Long> examIdList = redisService.getCacheListByRange(key, 0, - 1, Long.class);
        List<ExamVO> examVOList = assembleExamVOlist(examIdList, null, examListType);
        List<ExamVO> resultList = new ArrayList<>();
        LocalDateTime qStartTime = null;
        LocalDateTime qEndTime = null;
        if (StringUtils.isNotEmpty(startTime)) {
            qStartTime = LocalDateTime.parse(startTime, formatter);
        }
        if (StringUtils.isNotEmpty(endTime)) {
            qEndTime = LocalDateTime.parse(endTime, formatter);
        }
        int total = 0;
        for (ExamVO examVO : examVOList) {
            LocalDateTime examStartTime = examVO.getStartTime();
            LocalDateTime examVOEndTime = examVO.getEndTime();

            if (qStartTime != null && (examStartTime.isBefore(qStartTime) && !examStartTime.isEqual(qStartTime))) {
                continue;
            }
            if (qEndTime != null && (examVOEndTime.isAfter(qEndTime) && !examVOEndTime.isEqual(qEndTime))) {
                continue;
            }
            if (total >= start && total < end) {
                resultList.add(examVO);
            }
            total++;
            if (total >= end) {
                break;
            }
        }
        TableDataInfo info = TableDataInfo.empty();
        info.setTotal((long) total);
        info.setRows(resultList);
        return info;
    }

    public TableDataInfo selectHistoryExamList(int start, int end, Long userId) {
        String key = getHistoryListKey();
        return getExamList(ExamListType.EXAM_HISTORY_LIST, userId, key, start, end);
    }

    public TableDataInfo selectUserExamList(Long userId, int start, int end) {
        String key = getUserExamListKey(userId);
        return getExamList(ExamListType.EXAM_USER_LIST, userId, key, start, end);
    }

    public long addUserExamCache(Long userId, Long examId) {
        String key = getUserExamListKey(userId);
        return redisService.leftPushForList(key, examId);
    }

    /**
     * 获取竞赛基本信息
     * @param examId
     */
    public Exam selectExamBaseInfo(Long examId) {
        Exam exam = redisService.getCacheMapValue(getDetailKey(), String.valueOf(examId), Exam.class);
        if (exam != null) {
            return exam;
        }
        return refreshDetail(examId);
    }

    /**
     * 查看参与竞赛的用户排名
     *
     * @param start
     * @param end
     * @param examId 竞赛id
     * @return 排名信息
     */
    public TableDataInfo rankList(int start, int end, Long examId) {
        String key = getUserExamRankKey(examId);
        Long total = redisService.getListSize(key);
        if (total == null || total == 0) {
            refreshUserExamRank(key, examId);
        }
        total = redisService.getListSize(key);
        TableDataInfo info = TableDataInfo.empty();
        if (total == 0) {
            return info;
        }
        info.setTotal(total);
        List<ExamRankVO> examRankVOList = redisService.getCacheListByRange(key, start, end - 1, ExamRankVO.class);
        if (!CollectionUtils.isEmpty(examRankVOList)) {
            assembleExamRankList(examRankVOList);
        }
        info.setRows(examRankVOList);
        return info;
    }

    private void assembleExamRankList(List<ExamRankVO> examRankVOList) {
        for (ExamRankVO examRankVO : examRankVOList) {
            Long userId = examRankVO.getUserId();
            User user = userCacheManager.getUserById(userId);
            if (user != null) {
                examRankVO.setNickName(user.getNickName());
            }
        }
    }

    private void refreshUserExamRank(String key, Long examId) {
        List<UserExam> userExamList = userExamMapper.selectList(new LambdaQueryWrapper<UserExam>()
                .select(UserExam::getUserId, UserExam::getScore, UserExam::getExamRank)
                .eq(UserExam::getExamId, examId)
                .orderByAsc(UserExam::getExamRank));
        if (CollectionUtils.isEmpty(userExamList)) {
            return;
        }
        redisService.rightPushAll(key, userExamList);
    }

    private TableDataInfo getExamList(ExamListType examListType, Long userId, String key, int start, int end) {
        List<Long> examIdList = redisService.getCacheListByRange(key, start, end - 1, Long.class);
        long total;
        TableDataInfo info = TableDataInfo.empty();
        List<ExamVO> examVOList;
        if (CollectionUtils.isEmpty(examIdList)) {
            //未开赛 已开赛  用户参赛
            examVOList = refreshExamIdList(examListType, userId, key);
            if (CollectionUtils.isEmpty(examVOList)) {
                return info;
            }
            total = examVOList.size();
            if (start >= total - 1) {
                return info;
            }
            if (end >= total) {
                end = (int) total;
            }
            examVOList = examVOList.subList(start, end);
        } else {
            total = redisService.getListSize(key);
            examVOList = assembleExamVOlist(examIdList, userId, examListType);
        }
        info.setRows(examVOList);
        info.setTotal(total);
        return info;
    }

    private List<ExamVO> refreshExamIdList(ExamListType examListType, Long userId, String key) {
        //刷新缓存
        List<Exam> examList = new ArrayList<>();
        if (ExamListType.EXAM_NOT_STARTED_LIST.equals(examListType)) {
            //查询未开始列表
            examList = examMapper.selectList(new LambdaQueryWrapper<Exam>()
                    .ge(Exam::getStartTime, LocalDateTime.now())
                    .eq(Exam::getStatus, Constants.TRUE)
                    .orderByDesc(Exam::getStartTime));
        } else if (ExamListType.EXAM_HISTORY_LIST.equals(examListType)) {
            examList = examMapper.selectList(new LambdaQueryWrapper<Exam>()
                    .le(Exam::getStartTime, LocalDateTime.now())
                    .eq(Exam::getStatus, Constants.TRUE)
                    .orderByDesc(Exam::getStartTime));
        } else if (ExamListType.EXAM_USER_LIST.equals(examListType)) {
            if (userId == null) {
                return null;
            }
            examList = userExamMapper.selectUserExamList(userId);

        }
        if (CollectionUtils.isEmpty(examList)) {
            return null;
        }
        List<Long> userEntryExamList = null;
        if (!ExamListType.EXAM_USER_LIST.equals(examListType) && userId != null) {
            userEntryExamList = userExamMapper.selectUserEntryExam(userId);
        }
        List<Long> examIdList = new ArrayList<>();
        Map<String, Exam> examMap = new HashMap<>();
        List<ExamVO> examVOList = new ArrayList<>();
        for (Exam exam : examList) {
            examIdList.add(exam.getExamId());
            examMap.put(String.valueOf(exam.getExamId()), exam);
            ExamVO examVO = new ExamVO();
            BeanUtils.copyProperties(exam, examVO);
            if (ExamListType.EXAM_USER_LIST.equals(examListType)) {
                examVO.setEnter(true);
            } else if (!CollectionUtils.isEmpty(userEntryExamList) && userEntryExamList.contains(examVO.getExamId())) {
                examVO.setEnter(true);
            } else {
                examVO.setEnter(false);
            }
            examVOList.add(examVO);
        }
        redisService.rightPushAll(key, examIdList);
        redisService.setCacheMap(getDetailKey(), examMap);
        return examVOList;
    }

    private List<ExamVO> assembleExamVOlist(List<Long> examIdList, Long userId, ExamListType examListType) {
        Map<Long, Exam> examMap = getExamMap(examIdList);
        List<Long> userEntryExamList = null;
        if (!ExamListType.EXAM_USER_LIST.equals(examListType) && userId != null) {
            userEntryExamList = redisService.getCacheListByRange(getUserExamListKey(userId), 0, -1, Long.class);
        }
        List<ExamVO> examVOList = new ArrayList<>();
        for (Long examId : examIdList) {
            ExamVO examVO = new ExamVO();
            examVO.setExamId(examId);
            if (!CollectionUtils.isEmpty(userEntryExamList) && userEntryExamList.contains(examId)) {
                examVO.setEnter(true);
            } else {
                examVO.setEnter(false);
            }
            examVOList.add(examVO);
            if (examMap == null) {
                continue;
            }
            Exam exam = examMap.get(examId);
            if (exam == null) {
                continue;
            }
            examVO.setTitle(exam.getTitle());
            examVO.setStartTime(exam.getStartTime());
            examVO.setEndTime(exam.getEndTime());
        }
        return examVOList;
    }

    private Map<Long, Exam> getExamMap(List<Long> examIdList) {
        if (CollectionUtils.isEmpty(examIdList)) {
            return null;
        }
        Set<String> examIdStrSet = new HashSet<>();
        for (Long examId : examIdList) {
            examIdStrSet.add(String.valueOf(examId));
        }
        String examDetailKey = getDetailKey();
        List<Exam> examList = redisService.getMultiCacheMapValue(examDetailKey, examIdStrSet, Exam.class);
        if (CollectionUtils.isEmpty(examList) || examList.stream().allMatch(Objects::isNull)) {
            return refreshDetail(examIdList);
        }
        return examList.stream().collect(Collectors.toMap(Exam::getExamId, Function.identity()));
    }

    private Exam refreshDetail(Long examId) {
        Exam exam = examMapper.selectById(examId);
        if (exam != null) {
            redisService.setCacheMapValue(getDetailKey(), String.valueOf(examId), exam);
        }
        return exam;
    }

    private Map<Long, Exam> refreshDetail(List<Long> examIdSet) {
        List<Exam> examList = examMapper.selectList(new LambdaQueryWrapper<Exam>()
                .select(Exam::getExamId, Exam::getTitle, Exam::getStartTime, Exam::getEndTime)
                .in(Exam::getExamId, examIdSet));
        if (CollectionUtils.isEmpty(examList)) {
            return null;
        }
        Map<Long, Exam> examDetailMap = new HashMap<>();
        Map<String, Exam> examDetailCacheMap = new HashMap<>();
        for (Exam exam : examList) {
            examDetailMap.put(exam.getExamId(), exam);
            examDetailCacheMap.put(String.valueOf(exam.getExamId()), exam);
        }
        redisService.setCacheMap(getDetailKey(), examDetailCacheMap);
        return examDetailMap;
    }

    private String getUserExamListKey(Long userId) {
        return CacheConstants.EXAM_USER_LIST + userId;
    }

    private String getExamListKey() {
        return CacheConstants.EXAM_NOT_STARTED_LIST;
    }

    private String getHistoryListKey() {
        return CacheConstants.EXAM_HISTORY_LIST;
    }

    private String getDetailKey() {
        return CacheConstants.EXAM_DETAIL;
    }

    private String getUserExamRankKey(Long examId) {
        return CacheConstants.EXAM_USER_RANK_LIST + examId;
    }
}
